/**
 *  @file BinBuf.cpp
 */
#include "BinBuf.h"
#include "../common/CppMemPool.h"
#include <stdio.h>
#include <string.h>

namespace cppflib
{

namespace collections
{


//! buffer size unit in byte
#define UNITBUFSIZE 64

//! small buffer length
#define S_LEN_BUF 32

//! data alignment
#define DALIGN	8


BinBuf::BinBuf(void)
{
   InitInternal();
}

BinBuf::BinBuf(sz_t reqSize)
{
   InitInternal();
   Realloc(reqSize);
}

BinBuf::~BinBuf(void)
{
   if (this->pData)
      cppmempool::Release(this->pData);
}

void BinBuf::InitInternal(void)
{
   this->pData = NULL;
   this->len = this->validLen = 0;
}

void BinBuf::__Realloc(sz_t reqSize, bool isAlignBufferSize)
{
   if (this->len < reqSize) {
      
      // always allocate to multiple of UNITBUFSIZE or not ?
      sz_t tSize = (isAlignBufferSize) ? (((reqSize + UNITBUFSIZE - 1) / UNITBUFSIZE) * UNITBUFSIZE) : 
                                         reqSize;
         
      this->pData = static_cast<u8_t*>(::MRP_Realloc(this->pData, tSize));
      ::memset(&this->pData[this->len], 0, tSize - this->len);
      this->len = tSize;
   }
}

/**
 *  Reallocate buffer 
 *
 *  Buffer is allocated at a multiple of UNITBUFSIZE.
 *  Original data is copied to new buffer.
 *
 *  @param [in] reqSize -- request size in bytes
 *
 */
void BinBuf::Realloc(sz_t reqSize)
{
   __Realloc(reqSize, true);
}

/**
 *  Set the valid length and write a trailing '\0' at the end
 */
void BinBuf::SetValidLength(sz_t validLen)
{
    if (validLen <= this->len) {
       this->validLen = validLen;
       if (validLen < this->len)
          this->pData[validLen] = '\0';
    }
}

/**
 *  Copy data to binbuf. Memory is allocated if not enough.
 *  
 *  @param [in] pData -- data to be copied
 *  @param [in] dataLen -- length of data
 *  @param [in] isZeroTerm -- terminate the buffer by a zero or not
 *
 */
void BinBuf::CopyData(const u8_t * pData, sz_t dataLen, bool isZeroTerm)
{
   sz_t reqLen = isZeroTerm ? dataLen + 1 : dataLen;

   Realloc(reqLen);
   ::memcpy(this->pData, pData, dataLen);
   SetValidLength(dataLen);
}

/**
 *  Append data to binbuf. Memory is allocated if not enough.
 *  
 *  @param [in] pData -- data to be copied
 *  @param [in] dataLen -- length of data
 *  @param [in] isZeroTerm -- terminate the buffer by a zero or not
 *
 */
void BinBuf::AppendData(const u8_t * pData, sz_t dataLen, bool isZeroTerm)
{
   sz_t reqLen = isZeroTerm ? dataLen + 1 : dataLen;

   Realloc(this->validLen + reqLen);
   ::memcpy(this->pData + this->validLen, pData, dataLen);
   SetValidLength(this->validLen + dataLen);
}

/**
 *  Convert integer to printable string
 */
static int __IntToAString(const int val, char *szBuf, int bufSize, const char *szFormat)
{
#if defined(WIN32)
   return sprintf_s(szBuf, S_LEN_BUF, szFormat, val);
#elif defined(linux)
   return sprintf(szBuf, szFormat, val);
#endif
}

/**
 *  Append integer
 */
void BinBuf::AppendInt(const int val)
{
   char szVal[S_LEN_BUF];
   int nrByte = __IntToAString(val, szVal, S_LEN_BUF, "%d");
   AppendData(reinterpret_cast<u8_t*>(szVal), static_cast<sz_t>(nrByte), true);
}

/**
 *  Append unsigned integer
 */
void BinBuf::AppendUInt(const u32_t val)
{
   char szVal[S_LEN_BUF];
   int nrByte = __IntToAString((const int)val, szVal, S_LEN_BUF, "%u");
   AppendData(reinterpret_cast<u8_t*>(szVal), static_cast<sz_t>(nrByte), true);
}

/**
 *  Append hex integer
 */
void BinBuf::AppendHex(const u32_t val)
{
   char szVal[S_LEN_BUF];
   int nrByte = __IntToAString((const int)val, szVal, S_LEN_BUF, "%x");
   AppendData(reinterpret_cast<u8_t*>(szVal), static_cast<sz_t>(nrByte), true);
}

/**
 *  Reserve space at the end of this buffer
 *
 *  @param [in] reqSize -- size of reserved space in byte
 *
 *  @return offset to beginning of this buffer
 */
sz_t BinBuf::ReserveSpace(sz_t reqSize)
{
   sz_t retOffset = 0;   
   sz_t sizeForAlign = 0;

   // to ensure the returned pointer is properly aligned.
   if (this->validLen > 0 && this->validLen % DALIGN > 0) {
      sizeForAlign = DALIGN - (this->validLen % DALIGN);
      reqSize += sizeForAlign; // take it into account
   }

   if (GetSpaceLeft() < reqSize)
      Realloc(this->validLen + reqSize);
      
   retOffset = this->validLen + sizeForAlign;
   SetValidLength(this->validLen + reqSize);
   return retOffset;
}

/**
 *  Get end of valid data
 */
u8_t * BinBuf::GetValidEnd() const
{
   if (this->pData)
      return this->pData + this->validLen;

   return NULL;
}

/**
 *  Get end of buffer
 */
u8_t * BinBuf::GetBufferEnd() const
{
   if (this->pData)
      return this->pData + this->len;

   return NULL;
}

/**
 *  Get raw pointer to internal buffer
 */
u8_t * BinBuf::GetRawBuf() const
{
   return this->pData;
}

/**
 *  Get total length of buffer
 */
sz_t BinBuf::GetLength() const
{
   return this->len;
}

/**
 *  Get valid buffer length
 */
sz_t BinBuf::GetValidLength() const
{
   return this->validLen;
}

/**
 *  Get space left in bytes
 */
sz_t BinBuf::GetSpaceLeft() const
{
   return this->len - this->validLen;
}

/**
 *  Move data to the front.
 *
 *  @note If startOff points beyond the end of valid buffer, the 
 *        buffer's valid length will be set to 0.
 *
 *  @param [in] startOff -- starting offset to move data
 */
bool BinBuf::MoveData2Front(sz_t startOff)
{
   u8_t *pSrc, *pDest, *pSrcEnd;
   
   pDest = this->pData;
   pSrc = this->pData + startOff;
   pSrcEnd = GetValidEnd();

   if (pDest == pSrc)
      return false;

   while (pSrc < pSrcEnd) {
      *pDest++ = *pSrc++;
   }

   SetValidLength(static_cast<sz_t>(pDest - this->pData));

   return true;
}


/**
 *  Clear buffer to zero
 */
void BinBuf::ClearBuf()
{
   if (this->pData) {
      ::memset(this->pData, 0, this->len);
      SetValidLength(0);
   }
}


} // end of namespace collections


} // end of namespace cppflib


